<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
		<title>Hanging With Friends solution generator</title>
		<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
		<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
		<style type="text/css">
			body{font-family:sans-serif;margin:0;}
			#loading{background:#09c;bottom:0;color:#fff;font-size:2em;left:0;position:fixed;right:0;text-align:center;top:0;}
			.tile{border:1px solid #09c;padding:0;vertical-align:middle;}
			.tile,.tile input{text-align:center;}
			.tile input{border:none;margin:0;padding:0;height:2em;width:2em;font-size:1em;}
			.tile[data-tiletype]{color:#fff;font-weight:bold;height:2em;width:2em;}
			.tile[data-tiletype="0"]{background:transparent;}
			.tile[data-tiletype="1"]{background:blue;}
			.tile[data-tiletype="2"]{background:red;}
			.tile[data-tiletype="3"]{background:green;}
			.tile[data-tiletype="4"]{background:orange;}
			:invalid{color:red;}
			#statusbar{height:2em;background:#09c;width:0;}
			.solution{font-family:monospace;}
			p{border-left:solid 5px #999;color:#111;margin:5px;padding:1em .5em;}
			table{margin:1em 0;}
			input[type=button]{background:#09c;border:none;color:#fff;font-weight:bold;margin:1em;padding:.5em;}
		</style>

		<script type="text/javascript">



var tgd = tgd || {};
tgd.Utilities = tgd.Utilities || {};
tgd.Utilities.HangingWithFriends = tgd.Utilities.HangingWithFriends || {};



tgd.Utilities.HangingWithFriends.TileType = {
	Normal: 0,
	DL: 1,
	DW: 2,
	TL: 3,
	TW: 4
}



tgd.Utilities.HangingWithFriends.Letters = {
	getValue: function(letter) {
		letter = letter.toUpperCase();
		switch (letter) {
			case "A":
			case "E":
			case "I":
			case "O":
			case "R":
			case "S":
			case "T": return 1;
			case "D":
			case "L":
			case "N":
			case "U": return 2;
			case "G":
			case "H":
			case "Y": return 3;
			case "B":
			case "C":
			case "F":
			case "M":
			case "P":
			case "W": return 4;
			case "K":
			case "V": return 5;
			case "X": return 8;
			case "J":
			case "Q":
			case "Z": return 10;
			default: throw "Invalid letter:  " + letter;
		}
	}
}



tgd.Utilities.HangingWithFriends.Tile = function (ID, Value, Type) {
	var self = this;

	self.ID = ko.observable(ID);
	self.Value = ko.observable(Value);
	self.Type = ko.observable(Type);
	self.Label = ko.computed(function() {
		switch (self.Type()) {
			case tgd.Utilities.HangingWithFriends.TileType.DL: return "DL";
			case tgd.Utilities.HangingWithFriends.TileType.DW: return "DW";
			case tgd.Utilities.HangingWithFriends.TileType.TL: return "TL";
			case tgd.Utilities.HangingWithFriends.TileType.TW: return "TW";
			default: return "";
		}
	});
}



tgd.Utilities.HangingWithFriends.Solver = function () {
	var self = this;

	self.solutions = ko.observableArray([]);
	self.testStart = ko.observable(0);
	self.numWords = ko.observable(0);
	var num = 1000;

	self.percentageComplete = ko.computed(function() {
		return parseInt(self.testStart() * 100 / self.numWords());
	});

	var getScore = function(word, board) {
		var score = 0;

		for (var i = 0; i < word.length; ++i) {
			score += (board()[i].Type() === tgd.Utilities.HangingWithFriends.TileType.DL ? 2 : 1) * (board()[i].Type() === tgd.Utilities.HangingWithFriends.TileType.TL ? 3 : 1) * tgd.Utilities.HangingWithFriends.Letters.getValue(word[i]);
		}

		for (var i = 0; i < word.length; ++i) {
			score *= (board()[i].Type() === tgd.Utilities.HangingWithFriends.TileType.DW ? 2 : 1) * (board()[i].Type() === tgd.Utilities.HangingWithFriends.TileType.TW ? 3 : 1);
		}

		return score;
	}

	var checkSome = function(words, board, available, callback) {
		for (var i = self.testStart(); i < words.length && i < self.testStart() + num; ++i) {
			var word = words[i].toUpperCase();

			if (word.length > board().length || word.length < 4) { continue; }

			var avail = [];
			for (var x = 0; x < available().length; ++x) {
				avail[x] = true;
			}

			var isValid = false;

			for (var j = 0; j < word.length; ++j) {
				isValid = false;

				for (var k = 0; k < available().length; ++k) {
					if (avail[k] && word[j] === available()[k].Value()) {
						avail[k] = false;
						isValid = true;
						break;
					}
				}

				if (!isValid) {
					break;
				}
			}

			if (isValid) {
				self.solutions.push({ Word: word, Score: getScore(word, board) });
			}
		}

		if (i === words.length) {
			self.testStart(i);
			self.solutions.sort(function (a, b) { return b.Score - a.Score; });

			callback();
		} else {
			self.testStart(self.testStart() + num);
			setTimeout(function() { checkSome(words, board, available, callback); });
		}

	}

	self.getSolutions = function(words, board, available, callback) {
		self.solutions.removeAll();
		self.numWords(words.length);
		self.testStart(0);
		setTimeout(function() { checkSome(words, board, available, callback); });
	}
}



tgd.Utilities.HangingWithFriends.SolutionGeneratorViewModel = function () {
	var self = this;

	var words;

	self.boardtiles = ko.observableArray([]);
	self.availabletiles = ko.observableArray([]);
	self.solver = ko.observable(new tgd.Utilities.HangingWithFriends.Solver());

	for (var i = 0; i < 8; self.boardtiles.push(new tgd.Utilities.HangingWithFriends.Tile(i++, "", tgd.Utilities.HangingWithFriends.TileType.Normal)));
	for (var i = 0; i < 12; self.availabletiles.push(new tgd.Utilities.HangingWithFriends.Tile(i++, "", tgd.Utilities.HangingWithFriends.TileType.Normal)));

	self.toggleSpecialTile = function(tile) {
		var currentTile = self.boardtiles()[tile.ID()];

		currentTile.Type((currentTile.Type() + 1) % 5);

		for (var i = (tile.ID() + 1) % self.boardtiles().length; i != tile.ID(); i = (i + 1) % self.boardtiles().length) {
			self.boardtiles()[i].Type(tgd.Utilities.HangingWithFriends.TileType.Normal);
		}
	}

	self.generateSolutions = function() {
		//$("#loading").toggle(true);
		self.solver().getSolutions(words, self.boardtiles, self.availabletiles, function() {
			//$("#loading").toggle(false);
		});
	}

	self.setWords = function(w) {
		words = w;
		console.log("i have words");
	}

	self.validateInput = function(tile, event) {
		tile.Value(tile.Value().toUpperCase());
		if (!tile.Value().match(/[A-Z]/)) {
			tile.Value("");
		}
	}

	self.reset = function() {
		for (var i = 0; i < self.boardtiles().length; self.boardtiles()[i++].Type(tgd.Utilities.HangingWithFriends.TileType.Normal));
		for (var i = 0; i < self.availabletiles().length; self.availabletiles()[i++].Value(""));
	}
}



$(function() {
	var calculator = new tgd.Utilities.HangingWithFriends.SolutionGeneratorViewModel();
	ko.applyBindings(calculator);

	$("#loading").toggle(true);

	$.get("words.txt", function(data) {
		words = data.split("\n");
		calculator.setWords(words);
		$("#loading").toggle(false);
	}, "text");
});



		</script>

	</head>
	<body>
		<div id="loading">loading word list</div>
		<p>Click a tile to set the bonus tile for the board.</p>
		<table>
			<tr data-bind="foreach: boardtiles">
				<td class="tile" data-bind="text: Label, click: $parent.toggleSpecialTile, attr: {'data-tiletype': Type}"></td>
			</tr>
		</table>
		<p>Set your available tiles.</p>
		<table>
			<tr data-bind="foreach: availabletiles">
				<td class="tile">
					<input type="text" maxlength="1" size="1" data-bind="value: Value, event: { change: $parent.validateInput }" />
				</td>
			</tr>
		</table>
		<!-- TODO only show button when tiles are setup. -->
		<div>
			<input type="button" value="Generate Solutions" data-bind="click: generateSolutions" />
			<input type="button" value="Reset" data-bind="click: reset" />
		</div>
		<div id="solutions" data-bind="visible: solver().percentageComplete() > 0">
			<p>Calculating solutions.  (
				<span data-bind="text: solver().testStart"></span>
				/
				<span data-bind="text: solver().numWords"></span>
				)
			</p>
			<p>
				<span data-bind="text: solver().solutions().length"></span>
				solutions found.
			</p>
			<p>
				<span data-bind="text: solver().percentageComplete"></span>
				% complete.
			</p>
			<div id="statusbar" data-bind="style: { width: solver().percentageComplete() + '%' }"></div>
			<div data-bind="foreach: solver().solutions()">
				<p class="solution">
					<span data-bind="text: Score"></span>
					<span data-bind="text: Word"></span>
				</p>
			</div>
		</div>
	</body>
</html>